package com.yxx.service.Impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcloud.cos.demo.BucketDomainDemo;
import com.yxx.constant.CommonConstant;
import com.yxx.constant.MqConstant;
import com.yxx.constant.RedisConstant;
import com.yxx.entity.*;
import com.yxx.entityModel.dto.MailDTO;
import com.yxx.entityModel.vo.Result.PageResult;
import com.yxx.entityModel.vo.query.CommentQuery;
import com.yxx.entityModel.vo.request.CheckRequest;
import com.yxx.entityModel.vo.request.CommentRequest;
import com.yxx.entityModel.vo.response.*;
import com.yxx.enums.CommentTypeEnum;
import com.yxx.mapper.ArticleMapper;
import com.yxx.mapper.CommentMapper;
import com.yxx.mapper.TalkMapper;
import com.yxx.mapper.UserMapper;
import com.yxx.service.CommentService;
import com.yxx.service.EmailService;
import com.yxx.service.RedisService;
import com.yxx.service.SiteConfigService;
import com.yxx.utils.HTMLUtils;
import com.yxx.utils.PageUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.swing.text.html.parser.ContentModel;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * @author yuxiangxun
 * @date 2025/2/6
 * @apiNote
 */
@Slf4j
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {

    @Resource
    private CommentMapper commentMapper;

    @Resource
    private RedisService redisService;

    @Resource
    private ArticleMapper articleMapper;

    @Resource
    private TalkMapper talkMapper;

    @Resource
    private SiteConfigService siteConfigService;

    @Resource
    private UserMapper userMapper;

    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private EmailService emailService;

    @Value("${blog.url}")
    private String websiteUrl;

    /**
     * 查看后台评论列表
     * @param commentQuery
     * @return
     */
    @Override
    public PageResult<CommentBackResponse> commentBackList(CommentQuery commentQuery) {
        Long count = commentMapper.countComment(commentQuery);
        // 查询后台评论数量
        // 如果评论数量为0，则new一个分页结果对象并返回
        if(count == 0){
            return new PageResult<>();
        }
        // 如果评论数量不为0，则分页查询后台评论列表
        List<CommentBackResponse> commentBackResponseList = commentMapper.selectCommentBackList(commentQuery);
        return new PageResult<>(commentBackResponseList,count);
    }

    /**
     * 添加评论
     * @param commentRequest
     */
    @Override
    public void addComment(CommentRequest commentRequest) {
        // 校验评论参数
        verifyComment(commentRequest);
        // 获取网络配置
        SiteConfig siteConfig = siteConfigService.getSiteConfig();
        // 通过网络配置获取到评论审核设置
        Integer commentCheck = siteConfig.getCommentCheck();
        // 过滤评论不好的标签词语
        commentRequest.setCommentContent(HTMLUtils.filter(commentRequest.getCommentContent()));
        // 封装新评论
        Comment newComment = Comment.builder()
                .fromUid(StpUtil.getLoginIdAsInt())
                .toUid(commentRequest.getToUid())
                .typeId(commentRequest.getTypeId())
                .commentType(commentRequest.getCommentType())
                .parentId(commentRequest.getParentId())
                .replyId(commentRequest.getReplyId())
                .commentContent(commentRequest.getCommentContent())
                .isCheck(commentCheck.equals(CommonConstant.FALSE) ? CommonConstant.TRUE : CommonConstant.FALSE)
                .build();
        // 保存评论
        commentMapper.insert(newComment);
        // 查询评论用户昵称
        String fromNickname = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .select(User::getNickname)
                .eq(User::getId,StpUtil.getLoginIdAsInt())
        ).getNickname();
        // 通知用户
        if(siteConfig.getEmailNotice().equals(CommonConstant.TRUE)){
            // 异步执行通知任务，避免阻塞主线程
            CompletableFuture.runAsync(() -> notice(newComment,fromNickname), threadPoolTaskExecutor);
        }
    }

    /**
     * 校验评论参数
     * @param commentRequest
     */
    private void verifyComment(CommentRequest commentRequest){
        // 校验文章评论  如果该评论类型是文章类型的评论，校验文章是否存在
        if(commentRequest.getCommentType().equals(CommentTypeEnum.ARTICLE.getType())){
            Article article = articleMapper.selectOne(new LambdaQueryWrapper<Article>()
                    .select(Article::getId)
                    .eq(Article::getId,commentRequest.getTypeId())
            );
            // 如果评论的文章不存在，断言
            Assert.notNull(article,"文章不存在");
        }
        // 校验说说评论 如果该评论类型为说说类型的评论，校验说说是否存在
        if(commentRequest.getCommentType().equals(CommentTypeEnum.TALK.getType())){
            Talk talk = talkMapper.selectOne(new LambdaQueryWrapper<Talk>()
                    .select(Talk::getId)
                    .eq(Talk::getId,commentRequest.getTypeId())
            );
            // 如果评论的说说不存在，断言
            Assert.notNull(talk,"说说不存在");
        }
        // 评论为子评论，判断回复的评论和用户是否存在
        // 可以用Optional.ofNullable()判断是否是子评论，是子评论执行ifPresent()里面的逻辑，不是子评论的话直接跳过（这里逻辑只是处理子评论）
        Optional.ofNullable(commentRequest.getParentId()).ifPresent(
                parentId -> {
                    // 判断父评论是否存在
                    Comment parentComment = commentMapper.selectOne(new LambdaQueryWrapper<Comment>()
                            .select(Comment::getId, Comment::getParentId, Comment::getCommentType)
                            .eq(Comment::getId, parentId)
                    );
                    Assert.notNull(parentComment,"父评论不存在");
                    Assert.isNull(parentComment.getParentId(), "当前评论为子评论，不能作为父评论");
                    Assert.isTrue(commentRequest.getCommentType().equals(parentComment.getCommentType()),"只能以同类型的评论作为父评论");
                    // 判断回复的评论和回复用户否存在
                    Comment replyComment = commentMapper.selectOne(new LambdaQueryWrapper<Comment>()
                            .select(Comment::getId,Comment::getParentId,Comment::getFromUid,Comment::getCommentType)
                            .eq(Comment::getId, commentRequest.getReplyId()));
                    User toUser = userMapper.selectOne(new LambdaQueryWrapper<User>()
                            .select(User::getId)
                            .eq(User::getId, commentRequest.getToUid()));
                    Assert.notNull(replyComment,"回复的评论不存在");
                    Assert.notNull(toUser,"回复的用户不存在");
                    Assert.isTrue(commentRequest.getCommentType().equals(replyComment.getCommentType()),"只能回复同评论类型下的评论");
                    if(Objects.nonNull(replyComment.getParentId())){
                        Assert.isTrue(replyComment.getParentId().equals(parentId),"提交的评论parentId与当前回复评论parentId不一致");
                    }
                    Assert.isTrue(replyComment.getFromUid().equals(commentRequest.getToUid()),"提交的评论toUid与当前回复评论fromUid不一致");
                    // 只能回复当前父评论及子评论
                    List<Integer> replyIdList = commentMapper.selectCommentIdByParentId(parentId);
                    replyIdList.add(parentId);
                    Assert.isTrue(replyIdList.contains(parentId),"当前父评论下不存在该子评论");
                }
        );
    }

    /**
     * 评论通知
     * @param comment 评论
     * @param fromNickname 评论用户昵称
     */
    private void notice(Comment comment, String fromNickname){
        // 自己回复的评论不用通知自己
        if(comment.getFromUid().equals(comment.getToUid())){
            return;
        }
        // 邮件标题
        String title = "【 友链 】您收到一条新评论";
        // 回复用户id
        Integer toUid = CommonConstant.BLOGGER_ID;
        // 父评论
        if(Objects.isNull(comment.getParentId())){
            if(comment.getCommentContent().equals(CommentTypeEnum.ARTICLE.getType())){
                Article article = articleMapper.selectOne(new LambdaQueryWrapper<Article>()
                        .select(Article::getArticleTitle, Article::getUserId)
                        .eq(Article::getId, comment.getTypeId())
                );
                title = article.getArticleTitle();
                // 因为这里作评论通知，应该是要通知发表这篇文章的用户id
                toUid = article.getUserId();
            }
            if(comment.getCommentType().equals(CommentTypeEnum.TALK.getType())){
                title = "【说说】";
                // 通知的是接受到此评论的用户id
                toUid = talkMapper.selectOne(new LambdaQueryWrapper<Talk>()
                        .select(Talk::getUserId)
                        .eq(Talk::getId, comment.getTypeId())
                ).getUserId();
            }
            // 自己评论自己的文章或者说说，不用提醒
            if(comment.getFromUid().equals(toUid)){
                return;
            }

        }else{
            // 子评论  子评论的话，评论通知应该是收到通知的用户id
            toUid = comment.getToUid();
            if(comment.getCommentType().equals(CommentTypeEnum.ARTICLE.getType())){
                title = articleMapper.selectOne(new LambdaQueryWrapper<Article>()
                        .select(Article::getArticleTitle)
                        .eq(Article::getId, comment.getTypeId())
                ).getArticleTitle();
            }
            if(comment.getCommentType().equals(CommentTypeEnum.TALK.getType())){
                title = "说说";
            }
        }

        // 查询回复用户邮箱、昵称、id  因为回复用户要发评论消息给接收方才能收到评论提醒
        User toUser = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .select(User::getEmail, User::getNickname, User::getId)
                .eq(User::getId, toUid));
        // 邮箱不为空
        if(StringUtils.hasText(toUser.getEmail())){
            // 发送邮件  参数：评论，回复用户，标题，评论用户昵称
            sendEmail(comment,toUser,title,fromNickname);
        }
    }

    /**
     * 发送邮件
     * @param comment 评论
     * @param toUser  回复用户信息
     * @param title   标题
     * @param fromNickname  评论用户昵称
     */
    private void sendEmail(Comment comment, User toUser, String title, String fromNickname) {
        MailDTO mailDTO = new MailDTO();
        if (comment.getIsCheck().equals(CommonConstant.TRUE)) {
            Map<String, Object> contentMap = new HashMap<>(7);
            // 评论链接
            String typeId = Optional.ofNullable(comment.getTypeId())
                    .map(Object::toString)
                    .orElse("");
            String url = websiteUrl + CommentTypeEnum.getCommentPath(comment.getCommentType()) + typeId;
            // 父评论则提醒作者
            if (Objects.isNull(comment.getParentId())) {
                mailDTO.setToEmail(toUser.getEmail());
                mailDTO.setSubject(CommonConstant.COMMENT_REMIND);
                mailDTO.setTemplate(CommonConstant.AUTHOR_TEMPLATE);
                String createTime = DateUtil.formatLocalDateTime(comment.getCreateTime());
                contentMap.put("time", createTime);
                contentMap.put("url", url);
                contentMap.put("title", title);
                contentMap.put("nickname", fromNickname);
                contentMap.put("content", comment.getCommentContent());
                mailDTO.setContentMap(contentMap);
            } else {
                // 子评论则回复的是用户提醒该用户
                Comment parentComment = commentMapper.selectOne(new LambdaQueryWrapper<Comment>()
                        .select(Comment::getCommentContent, Comment::getCreateTime)
                        .eq(Comment::getId, comment.getReplyId()));
                mailDTO.setToEmail(toUser.getEmail());
                mailDTO.setSubject(CommonConstant.COMMENT_REMIND);
                mailDTO.setTemplate(CommonConstant.USER_TEMPLATE);
                contentMap.put("url", url);
                contentMap.put("title", title);
                String createTime = DateUtil.formatLocalDateTime(comment.getCreateTime());
                contentMap.put("time", createTime);
                // 被回复用户昵称
                contentMap.put("toUser", toUser.getNickname());
                // 评论用户昵称
                contentMap.put("fromUser", fromNickname);
                // 被回复的评论内容
                contentMap.put("parentComment", parentComment.getCommentContent());
                // 回复评论内容
                contentMap.put("replyComment", comment.getCommentContent());
                mailDTO.setContentMap(contentMap);
            }
            // 发送HTML邮件
//            rabbitTemplate.convertAndSend(MqConstant.EMAIL_EXCHANGE, MqConstant.EMAIL_HTML_KEY, mailDTO);
            CompletableFuture.runAsync(() -> emailService.sendHtmlMail(mailDTO), threadPoolTaskExecutor);
        } else {
            // 审核提醒
            String adminEmail = userMapper.selectOne(new LambdaQueryWrapper<User>()
                    .select(User::getEmail)
                    .eq(User::getId, CommonConstant.BLOGGER_ID)).getEmail();
            mailDTO.setToEmail(adminEmail);
            mailDTO.setSubject(CommonConstant.CHECK_REMIND);
            mailDTO.setContent("您收到一条新的回复，请前往后台管理页面审核");
            // 发送普通邮件
//            rabbitTemplate.convertAndSend(MqConstant.EMAIL_EXCHANGE, MqConstant.EMAIL_SIMPLE_KEY, mailDTO);
            CompletableFuture.runAsync(() -> emailService.sendSimpleMail(mailDTO), threadPoolTaskExecutor);
        }
    }




    /**
     * 审核评论
     * @param checkRequest
     */
    @Override
    public void updateCommentCheck(CheckRequest checkRequest) {
        // 修改评论审核状态  先获取评论Id结合，然后将每个集合转化为Comment对象，再通过批量修改方法修改数据库
        List<Comment> commentList = checkRequest.getIdList().stream()
                .map(id -> {
                    Comment build = Comment.builder()
                            .id(id)
                            .isCheck(checkRequest.getIsCheck())
                            .build();
                    return build;
                }).collect(Collectors.toList());
        // 批量修改评论审核状态
        this.updateBatchById(commentList);
    }

    /**
     * 查看最新评论
     * @return
     */
    @Override
    public List<RecentCommentResponse> RecentCommentList() {
        return commentMapper.selectRecentComment();
    }

    @Override
    public void removeByIds(List<Integer> commentIdList) {
        super.removeByIds(commentIdList);
    }

    @Override
    public PageResult<CommentResponse> commentList(CommentQuery commentQuery) {
        // 查询统计 评论数量
        Long count = commentMapper.selectCount(new LambdaQueryWrapper<Comment>()
                .eq(Objects.nonNull(commentQuery.getTypeId()),Comment::getTypeId,commentQuery.getTypeId())
                .eq(Comment::getCommentType,commentQuery.getCommentType())
                .eq(Comment::getIsCheck,commentQuery.getIsCheck())
                .isNull(Comment::getParentId)
        );
        // 如果评论数量为0 则一条评论都没有，返回一个空分页结果对象并返回
        if(count == 0){
            return new PageResult<>();
        }
        // 如果评论数量不为0，则分页查询父评论列表，如果父评论列表为空，返回一个空分页结果对象
        List<CommentResponse> parentCommentList = commentMapper.selectParentComment(commentQuery);
        if(CollectionUtils.isEmpty(parentCommentList)){
            return new PageResult<>();
        }

        // 还需要封装评论数据 likeCount (点赞数)  ReplyCount (回复数)  ReplyVOList (回复列表) 下面操作处理这三个属性需要封装
        // 思路：每个父评论都要封装 replyCount (回复数)  ReplyVOList (回复列表)  likeCount (点赞数)
        //      通过 Redis 中获取每条评论的点赞，并封装到 likeCountMap 中
        //      通过 父评论id集合 查询各个父评论的子评论(3条子评论即可)列表，封装好每条子评论的点赞数量，生成Map<父评论id,其子评论列表>
        //      根据父类id列表查询各个父类评论的回复数量，生成Map<父评论id，父评论回复数量>

        // 评论的点赞 从 Redis 中获取每条评论的点赞，并封装到 likeCountMap 中
        Map<String,Integer> likeCountMap = redisService.getHashAll(RedisConstant.COMMENT_LIKE_COUNT);
        // 父评论id集合
        List<Integer> parentCommentIdList = parentCommentList.stream()
                .map(CommentResponse::getId).collect(Collectors.toList());
        // 根据父评论id集合查询各自的子评论列表，每一条父评论查询出3条子评论即可
        List<ReplyResponse> replyResponseList = commentMapper.selectReplyByParentCommentIdList(parentCommentIdList);
        // 封装子评论的点赞数量  因为子评论也可以点赞
        replyResponseList.forEach(item -> {
            // 根据 likeCountMap 中 key 为评论id 获取点赞数量
            item.setLikeCount(
                    Optional.ofNullable(likeCountMap.get(item.getId().toString())).orElse(0)
            );
        });
        // 根据回复列表生成对应子评论的Map -> replyMap
        Map<Integer, List<ReplyResponse>> replyMap = replyResponseList.stream()
                .collect(Collectors.groupingBy(ReplyResponse::getParentId));
        // 父评论的回复数量 (根据父评论id查询回复数量)
        List<ReplyCountResponse> replyCountResponseList = commentMapper.selectReplyCountByParentId(parentCommentIdList);
        // 转换 Map  这里应该封装的是父评论的回复数量的Map -> replyCountMap
        Map<Integer,Integer> replyCountMap = replyCountResponseList.stream()
                .collect(Collectors.toMap(ReplyCountResponse::getCommentId,ReplyCountResponse::getReplyCount));
        // 封装评论数据
        parentCommentList.forEach(item -> {
            item.setLikeCount(Optional.ofNullable(likeCountMap.get(item.getId().toString())).orElse(0));
            item.setReplyVOList(replyMap.get(item.getId()));
            item.setReplyCount(Optional.ofNullable(replyCountMap.get(item.getId())).orElse(0));
        });
        return new PageResult<>(parentCommentList,count);
    }

    @Override
    public List<ReplyResponse> replyList(Integer commentId) {
        // 分页查询子评论
        List<ReplyResponse> replyResponseList = commentMapper.selectReplyByParentId(PageUtils.getLimit(),PageUtils.getSize(),commentId);
        // 子评论点赞Map  从 Redis 中获取子评论点赞数
        Map<String,Integer> likeCountMap = redisService.getHashAll(RedisConstant.COMMENT_LIKE_COUNT);
        // 封装每条子评论的点赞数
        replyResponseList.forEach(
                replyItem -> {
                    replyItem.setLikeCount(likeCountMap.get(replyItem.getId().toString()));
                }
        );
        return replyResponseList;
    }


}
